"""
Copyright © 2023 Walkline Wang (https://walkline.wang)
Gitee: https://gitee.com/walkline/micropython_ble_config
"""
__version__ = '0.1.1'  # 定义版本号
__version_info__ = (0, 1, 1)  # 定义版本信息元组
print('module ble_config version:', __version__)  # 打印模块版本信息

import struct  # 导入struct模块，用于处理二进制数据的打包和解包
import ubluetooth as bt  # 导入MicroPython的蓝牙模块，并命名为bt
from micropython import const  # 从micropython模块导入const，用于定义常量

# 定义BLE事件常量
IRQ_CENTRAL_CONNECT    = const(1)  # 中央设备连接事件
IRQ_CENTRAL_DISCONNECT = const(2)  # 中央设备断开连接事件
IRQ_GATTS_WRITE        = const(3)  # GATT服务器写事件

# 定义广告包数据类型常量
AD_TYPE_FLAGS                         = const(0x01)  # 广告数据标志
AD_TYPE_COMPLETE_LOCAL_NAME           = const(0x09)  # 完整的本地名称
AD_TYPE_16BIT_SERVICE_UUID_COMPLETE   = const(0x03)  # 完整的16位服务UUID
AD_TYPE_32BIT_SERVICE_UUID_COMPLETE   = const(0x05)  # 完整的32位服务UUID
AD_TYPE_128BIT_SERVICE_UUID_COMPLETE  = const(0x07)  # 完整的128位服务UUID
AD_TYPE_APPEARANCE                    = const(0x19)  # 外观

# 定义UUID
UART_UUID  = bt.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')  # UART服务UUID
RX_UUID    = bt.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E')  # 接收特征UUID
TX_UUID    = bt.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E')  # 发送特征UUID

# 定义本地设备名称
LOCAL_NAME = 'ble-config'

# 定义SSID和密码的前缀，用于接收到数据时区分是SSID还是密码
SSID_PREFIX     = b'ssid_'  # SSID前缀
PASSWORD_PREFIX = b'pswd_'  # 密码前缀

# 定义BLE配置类
class BLEConfig(object):
    def __init__(self, rx_received_cb=None):
        # 初始化BLEConfig类
        self.__ble = bt.BLE()  # 创建BLE对象
        self.__rx_received_cb = rx_received_cb  # 接收数据回调函数
        self.__conn_handle = None  # 连接句柄

        self.ssid = ''  # 存储接收到的SSID
        self.password = ''  # 存储接收到的密码

        self.__ble.active(False)  # 关闭BLE
        self.__ble.active(True)  # 激活BLE
        print('ble activated')  # 打印激活信息

        self.__ble.config(gap_name=LOCAL_NAME)  # 配置GAP名称
        self.__ble.irq(self.__irq)  # 设置BLE事件回调

        # 创建广告和响应数据包
        self.__adv_payload = self.__advertising_payload(services=(UART_UUID,))  # 广告数据包
        self.__resp_payload = self.__advertising_payload(name=LOCAL_NAME)  # 响应数据包

        self.__register_services()  # 注册服务
        self.__advertise()  # 开始广播

    def __register_services(self):
        # 定义UART服务，包括TX和RX特征
        UART_SERVICE = (
            UART_UUID, 
            (
                (TX_UUID, bt.FLAG_NOTIFY,),  # TX特征，具有通知标志
                (RX_UUID, bt.FLAG_WRITE,),  # RX特征，具有写入标志
            ),
        )

        # 注册服务并获取句柄
        (
            (
                self.__tx_handle,  # TX特征句柄
                self.__rx_handle,  # RX特征句柄
            ),
        ) = self.__ble.gatts_register_services((UART_SERVICE,))  # 注册UART服务并获取特征句柄

    def __advertise(self, interval_us=500000):
        self.__ble.gap_advertise(None)  # 停止广播
        self.__ble.gap_advertise(interval_us, adv_data=self.__adv_payload, resp_data=self.__resp_payload)  # 开始广播
        print('advertising...')  # 打印广播信息

    def __irq(self, event, data):
        if event == IRQ_CENTRAL_CONNECT:  # 处理中央设备连接事件
            self.__conn_handle, _, addr, = data  # 获取连接句柄和设备地址
            print(f'[{self.__decode_mac(addr)}] connected, handle: {self.__conn_handle}')  # 打印连接信息

            self.__ble.gap_advertise(None)  # 停止广播
        elif event == IRQ_CENTRAL_DISCONNECT:  # 处理中央设备断开事件
            self.__conn_handle, _, addr, = data  # 获取断开句柄和设备地址
            print(f'[{self.__decode_mac(addr)}] disconnected, handle: {self.__conn_handle}')  # 打印断开信息

            self.__conn_handle = None  # 重置连接句柄

            if not self.success():  # 如果没有成功连接，重新开始广播
                self.__advertise()
        elif event == IRQ_GATTS_WRITE:  # 处理GATT写事件
            conn_handle, value_handle = data  # 获取连接句柄和值句柄

            if conn_handle == self.__conn_handle and value_handle == self.__rx_handle:  # 检查是否是当前连接和RX特征
                data = bytes(self.__ble.gatts_read(self.__rx_handle))  # 读取数据

                if data.startswith(SSID_PREFIX):  # 检查是否是SSID数据
                    self.ssid = data[len(SSID_PREFIX):].decode('utf-8')  # 提取SSID
                elif data.startswith(PASSWORD_PREFIX):  # 检查是否是密码数据
                    self.password = data[len(PASSWORD_PREFIX):].decode('utf-8')  # 提取密码

                if self.__rx_received_cb:  # 调用接收数据回调函数
                    self.__rx_received_cb(data)

    def __decode_mac(self, addr):
        if isinstance(addr, memoryview):
            addr = bytes(addr)  # 将memoryview转换为bytes

        assert isinstance(addr, bytes) and len(addr) == 6, ValueError('mac address value error')  # 确认MAC地址格式
        return ':'.join(['%02X' % byte for byte in addr])  # 返回格式化的MAC地址

    def __advertising_payload(limited_disc=False, br_edr=False, name=None, services=None):
        payload = bytearray()  # 创建空的字节数组

        def _append(adv_type, value):
            nonlocal payload  # 引用外部变量
            payload += struct.pack('BB', len(value) + 1, adv_type) + value  # 添加广告数据

        # 添加广告标志
        _append(
            AD_TYPE_FLAGS,
            struct.pack('B', (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04))
        )

        if name:
            _append(AD_TYPE_COMPLETE_LOCAL_NAME, name)  # 添加设备名称

        if services:
            for uuid in services:
                b = bytes(uuid)
                if len(b) == 2:
                    _append(AD_TYPE_16BIT_SERVICE_UUID_COMPLETE, b)  # 添加16位UUID
                elif len(b) == 4:
                    _append(AD_TYPE_32BIT_SERVICE_UUID_COMPLETE, b)  # 添加32位UUID
                elif len(b) == 16:
                    _append(AD_TYPE_128BIT_SERVICE_UUID_COMPLETE, b)  # 添加128位UUID

        return payload  # 返回广告数据

    def success(self) -> bool:
        return len(self.ssid) > 0 and len(self.password) > 0  # 检查是否成功获取到SSID和密码

def run_test():
    import time  # 导入time模块

    def rx_received_cb(data):
        print(f'received rx data: {data}')  # 打印接收到的数据

    bleconfig = BLEConfig(rx_received_cb)  # 创建BLEConfig实例

    while not bleconfig.success():
        time.sleep(0.1)  # 等待配置成功

    print(f'ssid: {bleconfig.ssid}, password: {bleconfig.password}')  # 打印SSID和密码

if __name__ == '__main__':
    run_test()  # 运行测试

